////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2009 Daniel F. Hanson
// 
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// 
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
////////////////////////////////////////////////////////////////////////////////

using System;
using System.Reflection;

namespace SilverLua.Runtime
{
   /// <summary>
   /// Stores a dynamically typed value
   /// </summary>
   public class Value : IEquatable<Value>
   {
      private object _objectValue;
      private Table _tableValue;
      private int _intValue;
      private float _floatValue;

      /// <summary>
      /// Overridden to allow Values as keys.
      /// </summary>
      /// <returns>The hash code</returns>
      public override int GetHashCode()
      {
         if (Type == ValueType.Integer)
         {
            return _intValue;
         }
         else if (Type == ValueType.Float)
         {
            unsafe
            {
               fixed (float* fp = &_floatValue)
               {
                  return *(int*)fp;
               }
            }
         }
         else if (Type == ValueType.Table)
         {
            return _tableValue.GetHashCode();
         }
         else
         {
            return _objectValue.GetHashCode();
         }
      }
      
      /// <summary>
      /// Compares two Values and returns true only if the types match and the
      /// values match (i.e. no implicit conversion is performed).
      /// </summary>
      /// <param name="other">Value to compare against</param>
      /// <returns>True if Values are equal, false otherwise</returns>
      public bool Equals(Value other)
      {
         if (Type != other.Type) return false;
         
         switch (Type)
         {
            case ValueType.Nil: return true;
            case ValueType.Integer: return _intValue == other._intValue;
            case ValueType.Float: return _floatValue == other._floatValue;
            case ValueType.String: return (string)_objectValue == (string)other._objectValue;
            case ValueType.Boolean: return (_intValue != 0) == (other._intValue != 0);
            case ValueType.Table: return _tableValue == other._tableValue;
            default: return _objectValue == other._objectValue;
         }
      }
      
      /// <summary>
      /// Default constructor. Initializes the Value to nil.
      /// </summary>
      public Value()
      {
         _objectValue = null;
         _tableValue = null;
         _intValue = 0;
         _floatValue = 0;
         Type = ValueType.Nil;
      }
      
      /// <summary>
      /// Copy constructor.
      /// </summary>
      /// <param name="value">The Value to copy from</param>
      public Value(Value value)
      {
         if (value == null)
         {
            throw new ArgumentNullException("Cannot create Value from null");
         }

         _objectValue = value._objectValue;
         _tableValue = value._tableValue;
         _intValue = value._intValue;
         _floatValue = value._floatValue;
         Type = value.Type;
      }
      
      public Value(string value)
      {
         if (value == null)
         {
            throw new ArgumentNullException("Cannot create Value from null");
         }

         _objectValue = value;
         Type = ValueType.String;
      }
      
      public Value(int value)
      {
         _intValue = value;
         Type = ValueType.Integer;
      }
      
      public Value(float value)
      {
         _floatValue = value;
         Type = ValueType.Float;
      }
      
      public Value(bool value)
      {
         _intValue = value ? 1 : 0;
         Type = ValueType.Boolean;
      }
      
      public Value(Table table)
      {
         if (table == null)
         {
            throw new ArgumentNullException("Cannot create Value from null");
         }

         _tableValue = table;
         Type = ValueType.Table;
      }

      internal Value(MethodInfo method)
      {
         if (method == null)
         {
            throw new ArgumentNullException("Cannot create Value from null");
         }

         _objectValue = new HostMethod(method);
         Type = ValueType.HostMethod;
      }

      internal Value(ConstructorInfo ctor)
      {
         if (ctor == null)
         {
            throw new ArgumentNullException("Cannot create Value from null");
         }
         
         _objectValue = new HostConstructor(ctor);
         Type = ValueType.HostConstructor;
      }

      internal Value(FieldInfo field)
      {
         if (field == null)
         {
            throw new ArgumentNullException("Cannot create Value from null");
         }

         _objectValue = new HostField(field);
         Type = ValueType.HostField;
      }

      internal static Value FromMethodHelper(MethodInfo method)
      {
         if (method == null)
         {
            throw new ArgumentNullException("Cannot create Value from null");
         }

         Value value = new Value();
         value._objectValue = new HostMethodHelper(method);
         value.Type = ValueType.HostMethodHelper;
         
         return value;
      }
      
      internal Value(MethodInfo getter, MethodInfo setter)
      {
         if (getter == null && setter == null)
         {
            throw new ArgumentNullException("Must provide at least one of getter or setter");
         }

         _objectValue = new HostFieldHelper(getter, setter);
         Type = ValueType.HostFieldHelper;
      }
      
      internal Value(HostField field, object owner)
      {
         _objectValue = new HostField(field, owner);
         Type = ValueType.HostField;
      }
      
      internal Value(HostFieldHelper field, object owner)
      {
         _objectValue = new HostFieldHelper(field, owner);
         Type = ValueType.HostFieldHelper;
      }
      
      internal Value(HostProperty property, object owner)
      {
         _objectValue = new HostProperty(property, owner);
         Type = ValueType.HostProperty;
      }
      
      public Value(Function func)
      {
         if (func == null)
         {
            throw new ArgumentNullException("Cannot create Value from null");
         }

         _objectValue = func;
         Type = ValueType.Function;
      }
      
      public Value(object obj, Table prototype)
      {
         if (obj == null)
         {
            throw new ArgumentNullException("Cannot create Value from null");
         }
         
         _objectValue = obj;
         _tableValue = prototype.Clone();
         Type = ValueType.HostObject;
      }
      
      /// <summary>
      /// Creates a copy of this Value object. It does not create a copy of the
      /// value stored inside of this Value (i.e. not a deep copy).
      /// </summary>
      /// <returns>The copied Value</returns>
      public Value Clone()
      {
         return new Value(this);
      }
      
      public void SetToNil()
      {
         _objectValue = null;
         _tableValue = null;
         Type = ValueType.Nil;
      }
      
      public void SetValue(Value from)
      {
         _objectValue = from._objectValue;
         _tableValue = from._tableValue;
         _intValue = from._intValue;
         _floatValue = from._floatValue;
         Type = from.Type;
      }

      public void SetValue(int value)
      {
         if (IsHostProperty)
         {
            SetHostValue(value);
         }
         else
         {
            _objectValue = null;
            _tableValue = null;
            _intValue = value;
            Type = ValueType.Integer;
         }
      }

      public void SetValue(float value)
      {
         if (IsHostProperty)
         {
            SetHostValue(value);
         }
         else
         {
            _objectValue = null;
            _tableValue = null;
            _floatValue = value;
            Type = ValueType.Float;
         }
      }

      public void SetValue(string value)
      {
         if (IsHostProperty)
         {
            SetHostValue(value);
         }
         else
         {
            _objectValue = null;
            _tableValue = null;
            _objectValue = value;
            Type = ValueType.String;
         }
      }

      public void SetValue(bool value)
      {
         if (IsHostProperty)
         {
            SetHostValue(value);
         }
         else
         {
            _objectValue = null;
            _tableValue = null;
            _intValue = value ? 1 : 0;
            Type = ValueType.Boolean;
         }
      }

      public void SetValue(Table value)
      {
         if (IsHostProperty)
         {
            SetHostValue(value);
         }
         else
         {
            _objectValue = null;
            _tableValue = value;
            Type = ValueType.Table;
         }
      }
      
      public void SetValue(Function value)
      {
         if (IsHostProperty)
         {
            SetHostValue(value);
         }
         else
         {
            _tableValue = null;
            _objectValue = value;
            Type = ValueType.Function;
         }
      }
      
      public void SetValue(object hostObject, Table prototype)
      {
         if (IsHostProperty)
         {
            SetHostValue(hostObject);
            _tableValue = prototype;
         }
         else
         {
            _objectValue = hostObject;
            _tableValue = prototype;
            Type = ValueType.HostObject;
         }
      }
      
      /// <summary>
      /// Gets the type of the Value
      /// </summary>
      public ValueType Type { get; private set; }
      
      /// <summary>
      /// Gets the value for nil
      /// </summary>
      public static readonly Value Nil = new Value();
      
      /// <summary>
      /// Gets whether the value is nil
      /// </summary>
      public bool IsNil { get { return Type == ValueType.Nil; } }
      
      private object GetHostValue()
      {
         if (Type == ValueType.HostField)
         {
            HostField hostField = (HostField)_objectValue;
            FieldInfo field = hostField.FieldInfo;
            return field.GetValue(hostField.Owner);
         }
         else if (Type == ValueType.HostProperty)
         {
            HostProperty hostProperty = (HostProperty)_objectValue;
            if (hostProperty.IndexInfo.Length > 0)
            {
               throw new Exception("Cannot use indexer property without index parameter; use subscript syntax");
            }
            PropertyInfo property = hostProperty.PropertyInfo;
            return property.GetValue(hostProperty.Owner, null);
         }
         else if (Type == ValueType.HostFieldHelper)
         {
            HostFieldHelper hostField = (HostFieldHelper)_objectValue;
            return hostField.GetterDelegate(hostField.Owner);
         }
         
         return null;
      }
      
      private bool IsHostProperty { get { return Type == ValueType.HostField || Type == ValueType.HostProperty || Type == ValueType.HostFieldHelper; } }
      
      private void SetHostValue(object value)
      {
         if (Type == ValueType.HostField)
         {
            HostField hostField = (HostField)_objectValue;
            FieldInfo field = hostField.FieldInfo;
            field.SetValue(hostField.Owner, value);
         }
         else if (Type == ValueType.HostProperty)
         {
            HostProperty hostProperty = (HostProperty)_objectValue;
            if (hostProperty.IndexInfo.Length > 0)
            {
               throw new Exception("Cannot use indexer property without index parameter; use subscript syntax");
            }
            PropertyInfo property = hostProperty.PropertyInfo;
            property.SetValue(hostProperty.Owner, value, null);
         }
         else if (Type == ValueType.HostFieldHelper)
         {
            HostFieldHelper hostField = (HostFieldHelper)_objectValue;
            hostField.SetterDelegate(hostField.Owner, value);
         }
      }
      
      /// <summary>
      /// Evaluates the Value as though it were an Integer
      /// </summary>
      /// <returns>The Value casted as an Integer</returns>
      public int GetInteger()
      {
         object hostValue = GetHostValue();
         
         if (hostValue != null)
         {
            return Convert.ToInt32(hostValue);
         }
         else if (Type != ValueType.Integer) CoerceToInt();
         return _intValue;
      }

      /// <summary>
      /// Evaluates the Value as though it were a Float
      /// </summary>
      /// <returns>The Value casted as a Float</returns>
      public float GetFloat()
      {
         object hostValue = GetHostValue();

         if (hostValue != null)
         {
            return Convert.ToSingle(hostValue);
         }
         else if (Type != ValueType.Float) CoerceToFloat();
         return _floatValue;
      }

      /// <summary>
      /// Evaluates the Value as though it were a String
      /// </summary>
      /// <returns>The Value casted as a String</returns>
      public string GetString()
      {
         object hostValue = GetHostValue();

         if (hostValue != null)
         {
            return Convert.ToString(hostValue);
         }
         else if (Type != ValueType.String) CoerceToString();
         return (string)_objectValue;
      }

      /// <summary>
      /// Evaluates the Value as though it were a Boolean
      /// </summary>
      /// <returns>The Value casted as a Boolean</returns>
      public bool GetBoolean()
      {
         object hostValue = GetHostValue();

         if (hostValue != null)
         {
            return Convert.ToBoolean(hostValue);
         }
         else if (Type != ValueType.Boolean) CoerceToBoolean();
         return _intValue != 0;
      }

      /// <summary>
      /// Evaluates the Value as though it were a Function
      /// </summary>
      /// <returns>The Value casted as a Function</returns>
      public Function GetFunction()
      {
         object hostValue = GetHostValue();

         if (hostValue != null)
         {
            return (Function)hostValue;
         }
         else if (Type != ValueType.Function) throw new InvalidCastException(string.Format("Cannot cast from {0} to Function", Type.ToString()));
         return (Function)_objectValue;
      }

      /// <summary>
      /// Evaluates the Value as though it were a Table
      /// </summary>
      /// <returns>The Value casted as a Table</returns>
      public Table GetTable()
      {
         object hostValue = GetHostValue();

         if (hostValue != null)
         {
            return (Table)hostValue;
         }
         else if (Type != ValueType.Table && Type != ValueType.HostObject) throw new InvalidCastException(string.Format("Cannot cast from {0} to Table", Type.ToString()));
         return _tableValue;
      }

      #region Host-type accessors
      internal HostMethod GetHostMethod()
      {
         if (Type != ValueType.HostMethod) throw new InvalidCastException(string.Format("Cannot cast from {0} to HostMethod", Type.ToString()));
         return (HostMethod)_objectValue;
      }

      internal HostConstructor GetHostConstructor()
      {
         if (Type != ValueType.HostConstructor) throw new InvalidCastException(string.Format("Cannot cast from {0} to HostConstructor", Type.ToString()));
         return (HostConstructor)_objectValue;
      }

      internal HostField GetHostField()
      {
         if (Type != ValueType.HostField) throw new InvalidCastException(string.Format("Cannot cast from {0} to HostField", Type.ToString()));
         return (HostField)_objectValue;
      }
      
      internal HostProperty GetHostProperty()
      {
         if (Type != ValueType.HostProperty) throw new InvalidCastException(string.Format("Cannot cast from {0} to HostProperty", Type.ToString()));
         return (HostProperty)_objectValue;
      }

      internal HostMethodHelper GetHostMethodHelper()
      {
         if (Type != ValueType.HostMethodHelper) throw new InvalidCastException(string.Format("Cannot cast from {0} to HostMethodHelper", Type.ToString()));
         return (HostMethodHelper)_objectValue;
      }

      internal HostFieldHelper GetHostFieldHelper()
      {
         if (Type != ValueType.HostFieldHelper) throw new InvalidCastException(string.Format("Cannot cast from {0} to HostFieldHelper", Type.ToString()));
         return (HostFieldHelper)_objectValue;
      }

      internal object GetHostObject()
      {
         if (Type != ValueType.HostObject) throw new InvalidCastException(string.Format("Cannot cast from {0} to HostObject", Type.ToString()));
         return _objectValue;
      }
      #endregion
      
      #region Arithmetic operations
      public void Add(Value value)
      {
         switch (Type)
         {
            case ValueType.Integer:
            {
               Value op = value.Clone();
               _intValue += op.GetInteger();
            }
            break;
            case ValueType.Float:
            {
               Value op = value.Clone();
               _floatValue += op.GetFloat();
            }
            break;
            default: throw new Exception(string.Format("Cannot perform arithmetic operation with {0} type", Type.ToString()));
         }
      }
      
      public void Subtract(Value value)
      {
         switch (Type)
         {
            case ValueType.Integer:
            {
               Value op = value.Clone();
               _intValue -= op.GetInteger();
            }
            break;
            case ValueType.Float:
            {
               Value op = value.Clone();
               _floatValue -= op.GetFloat();
            }
            break;
            default: throw new Exception(string.Format("Cannot perform arithmetic operation with {0} type", Type.ToString()));
         }
      }
      
      public void Multiply(Value value)
      {
         switch (Type)
         {
            case ValueType.Integer:
            {
               Value op = value.Clone();
               _intValue *= op.GetInteger();
            }
            break;
            case ValueType.Float:
            {
               Value op = value.Clone();
               _floatValue *= op.GetFloat();
            }
            break;
            default: throw new Exception(string.Format("Cannot perform arithmetic operation with {0} type", Type.ToString()));
         }
      }
      
      public void Divide(Value value)
      {
         switch (Type)
         {
            case ValueType.Integer:
            case ValueType.Float:
            {
               Value op = value.Clone();
               _floatValue = GetFloat() / op.GetFloat();
            }
            break;
            default: throw new Exception(string.Format("Cannot perform arithmetic operation with {0} type", Type.ToString()));
         }
      }
      
      public void Exponent(Value value)
      {
         switch (Type)
         {
            case ValueType.Integer:
            {
               Value op = value.Clone();
               _intValue = (int)Math.Pow(_intValue, op.GetInteger());
            }
            break;
            case ValueType.Float:
            {
               Value op = value.Clone();
               _floatValue = (float)Math.Pow(_floatValue, op.GetFloat());
            }
            break;
            default: throw new Exception(string.Format("Cannot perform arithmetic operation with {0} type", Type.ToString()));
         }
      }
      
      public void Modulo(Value value)
      {
         switch (Type)
         {
            case ValueType.Integer:
            {
               Value op = value.Clone();
               _intValue %= op.GetInteger();
            }
            break;
            case ValueType.Float:
            {
               Value op = value.Clone();
               _floatValue = (float)Math.IEEERemainder(_floatValue, op.GetFloat());
            }
            break;
            default: throw new Exception(string.Format("Cannot perform arithmetic operation with {0} type", Type.ToString()));
         }
      }
      
      public void Negate()
      {
         switch (Type)
         {
            case ValueType.Integer:
            {
               _intValue = -_intValue;
            }
            break;
            case ValueType.Float:
            {
               _floatValue = -_floatValue;
            }
            break;
            default: throw new Exception(string.Format("Cannot perform arithmetic operation with {0} type", Type.ToString()));
         }
      }
      #endregion
      
      public void Concatenate(Value value)
      {
         if (Type == ValueType.Integer ||
             Type == ValueType.Float ||
             Type == ValueType.String)
         {
            CoerceToString();
            
            if (value.Type == ValueType.Integer ||
                value.Type == ValueType.Float ||
                value.Type == ValueType.String)
            {
               string str = (string)_objectValue;
               str += value.GetString();
            }
            else
            {
               throw new Exception(string.Format("Cannot perform concatenation operation with {0} type", value.Type.ToString()));
            }
         }
         else
         {
            throw new Exception(string.Format("Cannot perform concatenation operation with {0} type", Type.ToString()));
         }
      }
      
      public void Not()
      {
         CoerceToBoolean();
         _intValue = (_intValue != 0) ? 0 : 1;
      }
      
      #region Relational operations
      public void Less(Value value)
      {
         bool less = IsLess(value);
         
         Type = ValueType.Boolean;
         _intValue = less ? 1 : 0;
      }
      
      public void LessEqual(Value value)
      {
         bool less = IsLess(value) || Equals(value);
         
         Type = ValueType.Boolean;
         _intValue = less ? 1 : 0;
      }
      
      public void Equal(Value value)
      {
         bool less = Equals(value);

         Type = ValueType.Boolean;
         _intValue = less ? 1 : 0;
      }
      
      public void NotEqual(Value value)
      {
         bool less = !Equals(value);

         Type = ValueType.Boolean;
         _intValue = less ? 1 : 0;
      }
      
      private bool IsLess(Value value)
      {
         if (value.Type != ValueType.Integer &&
             value.Type != ValueType.Float &&
             value.Type != ValueType.String)
         {
            throw new Exception(string.Format("Cannot perform relational operation with {0}", value.Type.ToString()));
         }
         
         Value op = value.Clone();
         
         switch (Type)
         {
            case ValueType.Integer: return _intValue < op.GetInteger();
            case ValueType.Float: return _floatValue < op.GetFloat();
            case ValueType.String: return ((string)_objectValue).CompareTo(op.GetString()) < 0;
            default: throw new Exception(string.Format("Cannot perform relational operation with {0}", Type.ToString()));
         }
      }
      #endregion

      #region Type coercion
      private void CoerceToInt()
      {
         switch (Type)
         {
            case ValueType.Float:
               _intValue = (int)_floatValue;
               break;
            case ValueType.String:
               _intValue = Convert.ToInt32((string)_objectValue);
               break;
            case ValueType.Integer: break;
            default: throw new InvalidCastException(string.Format("Cannot cast from {0} to Integer", Type.ToString()));
         }

         Type = ValueType.Integer;
      }

      private void CoerceToFloat()
      {
         switch (Type)
         {
            case ValueType.Integer:
               _floatValue = (float)_intValue;
               break;
            case ValueType.String:
               _floatValue = Convert.ToSingle((string)_objectValue);
               break;
            case ValueType.Float: break;
            default: throw new InvalidCastException(string.Format("Cannot cast from {0} to Float", Type.ToString()));
         }

         Type = ValueType.Float;
      }

      private void CoerceToString()
      {
         switch (Type)
         {
            case ValueType.Integer:
               _objectValue = _intValue.ToString();
               break;
            case ValueType.Float:
               _objectValue = _floatValue.ToString();
               break;
            case ValueType.Boolean:
               _objectValue = _intValue != 0 ? bool.TrueString : bool.FalseString;
               break;
            case ValueType.String: break;
            default: throw new InvalidCastException(string.Format("Cannot cast from {0} to String", Type.ToString()));
         }

         Type = ValueType.String;
      }

      private void CoerceToBoolean()
      {
         switch (Type)
         {
            case ValueType.Nil:
               _intValue = 0;
               break;
            case ValueType.Float:
            case ValueType.Function:
            case ValueType.Integer:
            case ValueType.String:
            case ValueType.Table:
               _intValue = 1;
               break;
            case ValueType.Boolean:
               break;
            default:
               throw new InvalidCastException(string.Format("Cannot cast from {0} to String", Type.ToString()));
         }

         Type = ValueType.Boolean;
      }
      #endregion
   }
   
   /// <summary>
   /// The types of SilverLua values.
   /// </summary>
   public enum ValueType
   {
      /// <summary>
      /// Used for the value 'nil'
      /// </summary>
      Nil,
      /// <summary>
      /// 32-bit signed integer
      /// </summary>
      Integer,
      /// <summary>
      /// 32-bit floating point
      /// </summary>
      Float,
      /// <summary>
      /// Unicode UTF-16 string
      /// </summary>
      String,
      /// <summary>
      /// True or false
      /// </summary>
      Boolean,
      /// <summary>
      /// A SilverLua function
      /// </summary>
      Function,
      /// <summary>
      /// A table of SilverLua values
      /// </summary>
      Table,
      /// <summary>
      /// A C# method linked into SilverLua
      /// </summary>
      HostMethod,
      /// <summary>
      /// A C# object linked into SilverLua
      /// </summary>
      HostObject,
      /// <summary>
      /// A C# field linked into SilverLua
      /// </summary>
      HostField,
      /// <summary>
      /// A C# property linked into SilverLua
      /// </summary>
      HostProperty,
      /// <summary>
      /// A C# constructor linked into SilverLua
      /// </summary>
      HostConstructor,
      /// <summary>
      /// A C# method indirectly linked into SilverLua
      /// </summary>
      HostMethodHelper,
      /// <summary>
      /// A C# field indirectly linked into SilverLua
      /// </summary>
      HostFieldHelper
   }
}
